Skip to content

Session-row 'Mark issue done' context menu — close GitHub / transition Jira (#526)#527

Merged
dhilgaertner merged 1 commit into
mainfrom
feature/crow-526-mark-issue-done-menu
Jun 19, 2026
Merged

Session-row 'Mark issue done' context menu — close GitHub / transition Jira (#526)#527
dhilgaertner merged 1 commit into
mainfrom
feature/crow-526-mark-issue-done-menu

Conversation

@dhilgaertner

Copy link
Copy Markdown
Contributor

Closes #526

What

Adds a right-click context-menu item on a session row that moves the linked issue to its done/closed state in one click. On workspaces with no PR→issue automation, a merged PR doesn't auto-close its issue, so it had to be closed by hand in the provider UI — this removes that round-trip.

Provider Action
GitHub gh issue close <url>
GitLab glab issue close <number> --repo <slug>
Jira transition to the mapped completed status (#523 jiraStatusMap, "Done" fallback)
Corveil set status closed

How

  • New TaskBackend.closeTask(url:) — a required protocol method (not capability-gated; every provider can close an issue). Deliberately separate from setTaskStatus(.done), which for GitHub only runs a Projects-v2 board-column mutation and does not close the issue. Jira/Corveil reuse their existing setTaskStatus(.done) transition (so Jira honors Settings UI to map Crow ticket states ↔ Jira workflow statuses #523's per-workspace jiraStatusMap); GitHub/GitLab shell out to close.
  • IssueTracker.markIssueDone(sessionID:) mirrors markInReview: resolves the backend (Jira gets the matching workspace's statusMap; other providers detect provider + host from the ticket URL), closes best-effort (auth / transition-not-allowed / already-closed are logged and swallowed — no crash), then flips the Crow session to .completed on success so the row reflects the closed issue.
  • SessionListView — context-menu item shown only when the session has a linked ticket, with a provider-flavored label ("Close Issue" for GitHub/GitLab, "Mark Issue Done" for Jira/Corveil). Loading state (isMarkingIssueDone) cleaned up on session delete.

Branches on backend/capability, not if provider == .github (ADR 0005).

Decisions

  • Session reflection: on a successful close we also flip the Crow session to .completed (reusing the existing onCompleteSession path) so the row moves to the Completed section — one click does both.
  • Label: provider-specific wording matches each provider's vocabulary (close vs. workflow transition).

Testing

  • New per-backend closeTask tests: GitHub (close / invalid-URL / command-failure), GitLab (glab issue close argv + GITLAB_HOST), Jira (default "Done" + mapped statusMap["Done"]), Corveil (closed).
  • swift test in Packages/CrowProvider — 100 tests, 0 failures.
  • make app — full macOS app builds clean.

🤖 Generated with Claude Code

…n Jira (#526)

Add a right-click item on a session row that moves the linked issue to its
done/closed state in one click, removing the manual round-trip to the provider
UI when no PR→issue automation closes it on merge.

- New `TaskBackend.closeTask(url:)` — required (not capability-gated; every
  provider can close). Deliberately distinct from `setTaskStatus(.done)`, which
  for GitHub only moves a Projects-v2 board column and does NOT close the issue.
  - GitHub  → `gh issue close <url>`
  - GitLab  → `glab issue close <number> --repo <slug>`
  - Jira    → reuse `setTaskStatus(.done)` → transitions to the mapped completed
              status (#523 `jiraStatusMap`, "Done" fallback)
  - Corveil → reuse `setTaskStatus(.done)` → `closed`
- IssueTracker.markIssueDone(sessionID:) mirrors markInReview: resolves the
  backend (Jira gets the per-workspace statusMap; others detect host from the
  URL), closes best-effort (auth / transition-not-allowed / already-closed are
  logged, non-fatal), then flips the Crow session to .completed on success.
- SessionListView: context-menu item shown only when the session has a linked
  ticket, with a provider-flavored label ("Close Issue" for GitHub/GitLab,
  "Mark Issue Done" for Jira/Corveil).
- Per-backend closeTask tests (GitHub/GitLab/Jira/Corveil).

Branch on backend/capability, not `if provider == .github` (ADR 0005).

Closes #526

🐦‍⬛ Generated with Claude Code, orchestrated by Crow

Co-Authored-By: Claude <noreply@anthropic.com>
Crow-Session: EFCDC679-23F6-4405-8A81-75E7DCA12833
@dhilgaertner dhilgaertner requested a review from dgershman as a code owner June 19, 2026 17:51
@dhilgaertner dhilgaertner added the crow:merge Crow auto-merge on green label Jun 19, 2026

@dgershman dgershman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code & Security Review

Critical Issues

None.

Security Review

Strengths:

  • closeTask validates URL shape (ProviderManager.parseTicketURLComponents / JiraKey.parse / CorveilTaskID.parse) before shelling out — no unsanitized URL flows to the CLI as an argv slot.
  • gh issue close <url> / glab issue close <iid> --repo <slug> use positional args (no command interpolation) and shellRunner.run(args:) is argv-based, not shell-string — no injection surface on hostile ticket URLs.
  • Auth errors (gh/glab/acli/corveil not authenticated) are caught and logged at the IssueTracker layer; no stack-crash on missing credentials.
  • gh issue close / glab issue close are idempotent — re-closing an already-closed issue is a no-op exit-0, so a stale row doesn't fail the user.

Concerns:

  • None identified. Provider-scope failures (e.g. PAT missing repo scope) fall through the generic catch and log without crashing — the session simply doesn't flip to .completed. Acceptable best-effort behavior.

Code Quality

  • Wiring is symmetric with markInReview: AppState callback + loading map, AppDelegate registration, IssueTracker.markIssueDone, SessionService.deleteSession cleanup, SessionListView button. Five-touchpoint pattern mirrors the existing path — easy to maintain.
  • Backend dispatch correctly diverges from markInReview: Jira goes through taskBackend(for: .jira, jira: cfg) to thread statusMap (#523); other providers go through taskBackend(forURL:) so GitLab self-hosted gets the right GITLAB_HOST (unlike markInReview, which capability-gates out before that matters). Right call.
  • closeTask is correctly NOT capability-gated (ADR 0005 in spirit): every provider has a closed state, and the docstring on the protocol method explains why it's distinct from setTaskStatus(.done) (which for GitHub only moves the Projects-v2 column).
  • Provider-flavored label ("Close Issue" vs "Mark Issue Done") matches each provider's vocabulary. Nice touch.
  • Tests cover the new surface well: GitHub (happy / invalid URL / command failure), GitLab (argv + GITLAB_HOST env), Jira (default Done + mapped statusMap["Done"]), Corveil (closed status). swift test in CrowProvider runs 52 tests clean locally.

Consider (Green-level polish, not blocking):

  • IssueTracker.markIssueDone updates assignedIssues[idx].projectStatus = .done after success but leaves state = "open" on the local row. Same minor staleness as markInReview — the next refresh poll corrects it. Updating both inline would shave a couple of seconds of stale UI without changing semantics.
  • markInReview catches ProviderError.insufficientScope and calls reportScopeWarning("project") to surface a UI message; markIssueDone lacks that branch, so a gh-side scope failure logs to console but doesn't notify the user. Low priority — the action is best-effort and visible idle state already conveys "no-op".
  • SessionListView.sessionContextMenu gates the button on session.ticketURL != nil but not session.provider != nil. markIssueDone guards on session.provider and returns silently when missing, so a session with a ticket URL but no provider would render a clickable no-op item. Probably unreachable in practice (provider is derived at ticket-set time), but adding session.provider != nil to the visibility predicate would be cleaner.
  • markIssueDoneTitle(for:) collapses .none and .jira/.corveil to "Mark Issue Done" — could be written as default: return "Mark Issue Done" for one fewer line. Stylistic.

Summary Table

Color Meaning Verdict effect
Red Must fix Request changes
Yellow Should fix Request changes
Green Consider Approve allowed

Findings: 0 Red, 0 Yellow, 4 Green.

Recommendation: Approve — driven by [0 Red, 0 Yellow, 4 Green] findings. Clean implementation, consistent with existing patterns, well-tested.


🐦‍⬛ Reviewed by Crow via Claude Code

@dhilgaertner dhilgaertner merged commit 7d60c65 into main Jun 19, 2026
2 checks passed
@dhilgaertner dhilgaertner deleted the feature/crow-526-mark-issue-done-menu branch June 19, 2026 18:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

crow:merge Crow auto-merge on green

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Session row right-click: 'Mark issue done' — close GitHub issue / transition Jira to mapped completed status

2 participants